#ifndef S21_CONTAINER_VECTOR_H_
#define S21_CONTAINER_VECTOR_H_

#include <initializer_list>
#include <stdexcept>

namespace s21 {

template <typename T>
class vector {
 public:
  using value_type = T;
  using reference = value_type&;
  using rv_reference = value_type&&;
  using const_reference = const value_type&;
  using iterator = value_type*;
  using const_iterator = const value_type*;
  using size_type = std::size_t;

  vector() : data_(nullptr), size_(0), capacity_(0) {}

  explicit vector(size_type n)
      : data_(alloc.allocate(n)), size_(0), capacity_(n) {
    for (; size_ < n; size_++) {
      alloc.construct(data_ + size_, value_type());
    }
  }

  vector(std::initializer_list<T> const& items)
      : data_(alloc.allocate(items.size())), size_(0), capacity_(items.size()) {
    for (auto it = items.begin(); it != items.end(); ++it) {
      alloc.construct(data_ + size_, *it);
      size_++;
    }
  }

  vector(const vector& other)
      : data_(alloc.allocate(other.capacity_)),
        size_(0),
        capacity_(other.capacity_) {
    for (auto it = other.cbegin(); it != other.cend(); ++it) {
      alloc.construct(data_ + size_, *it);
      size_++;
    }
  }

  vector(vector&& other) noexcept
      : data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
    other.data_ = nullptr;
    other.size_ = 0;
    other.capacity_ = 0;
  }

  ~vector() {
    clear();
    alloc.deallocate(data_, capacity_);
  }

  vector& operator=(const vector& other) {
    if (this != &other) {
      vector tmp{other};
      swap(tmp);
    }

    return *this;
  }

  vector& operator=(vector&& other) noexcept {
    if (this != &other) {
      vector tmp{std::move(other)};
      swap(tmp);
    }

    return *this;
  }

  reference at(size_type index) {
    if (index >= size_) {
      throw std::out_of_range("vector.at: index out of range.");
    }

    return data_[index];
  }

  const_reference at(size_type index) const {
    if (index >= size_) {
      throw std::out_of_range("vector.at: index out of range.");
    }

    return data_[index];
  }

  reference operator[](size_type index) { return data_[index]; }

  const_reference operator[](size_type index) const { return data_[index]; }

  const_reference front() const { return data_[0]; }

  const_reference back() const { return data_[size_ - 1]; }

  value_type* data() { return data_; }

  iterator begin() { return data_; }

  iterator end() { return data_ + size_; }

  const_iterator cbegin() const { return data_; }

  const_iterator cend() const { return data_ + size_; }

  bool empty() const noexcept { return size_ == 0; }

  size_type size() const noexcept { return size_; }

  size_type max_size() const { return SIZE_MAX / sizeof(value_type); }

  size_type capacity() const noexcept { return capacity_; }

  void reserve(size_type new_capacity) {
    if (new_capacity > capacity_) {
      if (new_capacity + capacity_ > max_size()) {
        throw std::length_error(
            "vector.reserve: the new container size is too large.");
      }

      realloc(new_capacity);
    }
  }

  void shrink_to_fit() {
    if (size_ < capacity_) {
      realloc(size_);
    }
  }

  void clear() {
    for (auto it = begin(); it != end(); ++it) {
      alloc.destroy(it);
    }

    size_ = 0;
  }

  iterator insert(iterator pos, const_reference value) {
    return insert_many(pos, value);
  }

  iterator insert(iterator pos, rv_reference value) {
    return insert_many(pos, std::move(value));
  }

  void erase(iterator pos) {
    std::move(std::next(pos), end(), pos);
    pop_back();
  }

  void push_back(const_reference value) {
    reserve(capacity_ == 0 ? 5 : size_ >= capacity_ ? size_ * 2 : capacity_);
    alloc.construct(data_ + size_, value);
    size_++;
  }

  void push_back(rv_reference value) {
    reserve(capacity_ == 0 ? 5 : size_ >= capacity_ ? size_ * 2 : capacity_);
    alloc.construct(data_ + size_, std::move(value));
    size_++;
  }

  void pop_back() {
    alloc.destroy(std::prev(end()));
    size_--;
  }

  void swap(vector& other) noexcept {
    std::swap(data_, other.data_);
    std::swap(size_, other.size_);
    std::swap(capacity_, other.capacity_);
  }

  template <typename... Args>
  iterator insert_many(const_iterator pos, Args&&... args) {
    size_type pos_index = pos - cbegin();
    size_type n = sizeof...(args);
    vector tmp;
    tmp.reserve(size_ + n > capacity_ ? (size_ + n) * 2 : capacity_);

    for (size_type i = 0; i < pos_index; ++i) {
      tmp.push_back(std::move(data_[i]));
    }

    (tmp.push_back(std::forward<Args>(args)), ...);

    for (size_type i = pos_index; i < size_; ++i) {
      tmp.push_back(std::move(data_[i]));
    }

    swap(tmp);

    return std::next(begin(), pos_index);
  }

  template <typename... Args>
  void insert_many_back(Args&&... args) {
    if (size_ + sizeof...(args) > capacity_) {
      insert_many(cend(), std::forward<Args>(args)...);
    } else {
      (push_back(std::forward<Args>(args)), ...);
    }
  }

  template <typename... Args>
  void insert_many_front(Args&&... args) {
    insert_many(cbegin(), std::forward<Args>(args)...);
  }

 private:
  value_type* data_;
  size_type size_;
  size_type capacity_;
  std::allocator<value_type> alloc;

  void realloc(size_type new_capacity) {
    value_type* new_data = alloc.allocate(new_capacity);
    size_type shift = 0;

    for (auto it = begin(); it != end(); ++it) {
      alloc.construct(new_data + shift, std::move(*it));
      alloc.destroy(it);
      shift++;
    }

    alloc.deallocate(data_, capacity_);
    data_ = new_data;
    capacity_ = new_capacity;
  }
};

}  // namespace s21

#endif  // S21_CONTAINER_VECTOR_H_